package org.shalma.internal.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.shalma.model.Entity;
import org.shalma.persistence.Query;

public class MetaEntity {

	public static final String CLASS_PROPERTY = Query.CLASS_PROPERTY;
	static final String VERSION_PROPERTY = "V";
	static Field ID_FIELD;
	static Field VERSION_FIELD;

	static final String GET_PREFIX = "get";
	static final String SET_PREFIX = "set";
	static final String IS_PREFIX = "is";

	private byte[] classId;
	private final Class<?> type;
	private Constructor<?> constructor;
	private final List<Property> properties;
	private List<Attribute> declaredAttributes;
	private List<Attribute> attributes;

	private MetaEntity parent;

	public MetaEntity(Class<? extends Entity> type) throws SecurityException,
			NoSuchMethodException {

		this.type = type;
		classId = IdGenerator.getId(type);

		if (!type.equals(Entity.class))
			parent = MetaEntityRegistry.getInstance().getMetaEntity(
					type.getSuperclass());

		constructor = type.getDeclaredConstructor();
		constructor.setAccessible(true);

		properties = new ArrayList<Property>();
		for (Class<?> clazz = type; clazz != Entity.class; clazz = clazz
				.getSuperclass()) {

			for (Field field : clazz.getDeclaredFields()) {

				final int modifiers = field.getModifiers();
				if (Modifier.isStatic(modifiers)
						|| Modifier.isTransient(modifiers))
					continue;

				properties.add(new Property(field));
			}
		}

		declaredAttributes = new ArrayList<Attribute>();
		processMethods();
	}

	public static void setId(Entity entity, String id) {

		try {

			if (ID_FIELD == null) {

				ID_FIELD = Entity.class.getDeclaredField("id");
				ID_FIELD.setAccessible(true);
			}

			ID_FIELD.set(entity, id);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public void setVersion(Entity entity, long version) {

		try {

			if (VERSION_FIELD == null) {

				VERSION_FIELD = Entity.class.getDeclaredField("version");
				VERSION_FIELD.setAccessible(true);
			}

			VERSION_FIELD.set(entity, version);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public byte[] getClassId() {
		return classId;
	}

	public MetaEntity getParent() {

		return parent;
	}

	public Constructor<?> getConstructor() {
		return constructor;
	}

	public Property getProperty(byte[] id) {

		for (Property property : properties)
			if (Arrays.equals(id, property.getId()))
				return property;

		return null;
	}

	public List<Property> getProperties() {
		return properties;
	}

	public Attribute getAttribute(String name) {

		for (Attribute attribute : declaredAttributes)
			if (attribute.getName().equals(name))
				return attribute;

		if (parent != null)
			return parent.getAttribute(name);

		return null;
	}

	public List<Attribute> getAttributes() {
		return declaredAttributes;
	}

	void processMethods() {

		for (Method method : type.getDeclaredMethods()) {

			final int modidifiers = method.getModifiers();
			if (!Modifier.isPublic(modidifiers)
					|| Modifier.isStatic(modidifiers))
				continue;

			String name = method.getName();
			Class<?> resultType = method.getReturnType();
			int argCount = ((Class[]) method.getParameterTypes()).length;

			if (argCount == 0) {
				if (name.startsWith(GET_PREFIX))
					getOrCreateAttribute(name.substring(3)).setReader(method);
				else if (resultType == boolean.class
						&& name.startsWith(IS_PREFIX))
					getOrCreateAttribute(name.substring(2)).setReader(method);
			} else if (argCount == 1 && resultType == void.class
					&& name.startsWith(SET_PREFIX)) {
				getOrCreateAttribute(name.substring(3)).setWriter(method);
			}
		}
	}

	private Attribute getOrCreateAttribute(String name) {

		Attribute attribute = getAttribute(name);

		if (attribute == null) {
			attribute = new Attribute(name);
			declaredAttributes.add(attribute);
		}

		return attribute;
	}
}
